#! /usr/bin/python
import select
import socket
import sys
import signal
import traceback
import time
from threading import Thread
from cmd import *

BUFSIZ = 1024
DEFAULT_PORT = 777

class Client(SocketAgent):
    def __init__(self, sock):
        super(Client, self).__init__()
        self.socket = sock
        self.nickname = u''
        self.isLogin = False
        
    def sendcmd(self, cmd):
        msg = EncodeCmd2Client(cmd)
        self.sendmsg(msg)
        
    def recvcmd(self):
        return DecodeCmdFromClient( self.recvline(includeNewLine=True) )
        
        
class ChatRoomThread(Thread):
    '''
    support 2 Client to communicate directly
    '''
    def __init__(self, client1, client2, callback):
        Thread.__init__(self)
        self.sock = [client1.socket, client2.socket]
        self.callback = callback
        self.clients = (client1, client2)
        self.senddata = {client1.socket : '', client2.socket : ''}
        self.start()
    
    def close(self):
        for s in self.sock:
            s.close()
    
    def run(self):
        running = True
        
        while running:
            readyRead, readyWrite, inError = select.select(self.sock, self.sock, [])
            
            for s in readyRead:
                try:
                    data = s.recv(BUFSIZ)
                    if data:
                        another = self.sock[1] if s is self.sock[0] else self.sock[0]
                        self.senddata[another] += data
                    else:
                        print 'ChatRoomThread Ending....'
                        print 'FileNo:%d try to disconnect...' % s.fileno()
                        running = False
                        
                except socket.error, e:
                    running = False
                    
            for s in readyWrite:
                try:
                    if self.senddata[s]:
                        size = s.send(self.senddata[s])
                        if size == len(self.senddata[s]):
                            self.senddata[s] = ''
                        else:
                            self.senddata[s] = self.senddata[size:]
                        
                except socket.error, e:
                    running = False
                
                time.sleep(0.01)
                
        self.callback(self, 'End')
        

class FalldogServer(object):
    """ Simple chat server use non-blocking select """
    def __init__(self, port=DEFAULT_PORT, backlog=1000):
        
        self.clientmap = {} # Client map
        self.socks = [] # socket list
        self.lstChatRoom = [] #chat room list
        self.lstWantChatClient = []
        
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server.bind(('',port))
        print 'Falldog Server, Listening to port %d ...' % port
        self.server.listen(backlog)
        
        # Trap keyboard interrupts
        signal.signal(signal.SIGINT, self.sighandler)
        
    def sighandler(self, signum, frame):
        # Close the server
        print 'Shutting down server...'
        # Close existing client sockets
        for s in self.socks:
            s.close()
            
        self.server.close()

    def __mixin_callback(self, thread, task):
        if task == 'End':
            print 'a chat partner leave... fID=%d, fID=%d' % (thread.sock[0].fileno(),thread.sock[1].fileno())
            thread.close()
            self.lstChatRoom.remove(thread)
    
    def addClient(self, socket):
        self.clientmap[socket] = Client(socket)
        self.socks.append(socket)
    
    def removeClient(self, socket):
        del self.clientmap[socket]
        self.socks.remove(socket)
        socket.close()
    
    def createChatRoom(self, c1, c2):
        print 'Create Chat room for fID=%d, fID=%d' % (c1.fno, c2.fno)
        t = ChatRoomThread(c1, c2, self.__mixin_callback)
        self.lstChatRoom.append(t)
        self.socks.remove(c1.socket)
        self.socks.remove(c2.socket)
        
        c1.sendcmd(CMD_S_START)
        c2.sendcmd(CMD_S_START)
    
    def talk2client(self, s, isReadyRead, isReadyWrite):
        '''
        s: client socket
        '''
        #print 'client fno=%d, r=%s, w=%s\n' % (s.fileno(), bool(isReadyRead), bool(isReadyWrite))
        client = self.clientmap[s]
        if not client.isLogin:
            if isReadyRead:
                cmd = client.recvcmd()
                if cmd == CMD_C_LOGIN:
                    client.isLogin = True
                    client.sendcmd(CMD_S_LOGIN_OK)
                else:
                    client.sendcmd(CMD_S_LOGIN_FAIL)
                    raise ValueError("[Server] Client pass srong parameter for login")
            return
        
        if isReadyRead:
            cmd = client.recvcmd()
            if cmd == CMD_C_NICKNAME:
                self.client.nickname = client.recvline()
            
            elif cmd == CMD_C_WANTCHAT:
                if len(self.lstWantChatClient)==1:
                    c = self.lstWantChatClient.pop()
                    self.createChatRoom(c, client)
                else:
                    self.lstWantChatClient.append(client)
                    
    def serve(self):
        self.socks = []
        
        running = True
        
        while running:
            
            readyRead, readyWrite, inError = select.select(self.socks+[self.server], self.socks, [])
            
            if self.server in readyRead:
                # handle the server socket
                csocket, address = self.server.accept()
                print 'FalldogServer: got connection fID=%d from %s' % (csocket.fileno(), address)
                self.addClient(csocket)
                
            
            READ  = 0x01
            WRITE = 0x10
            readyClient = {}
            for s in readyRead:
                if s is self.server:
                    continue
                readyClient[s] = READ
            for s in readyWrite:
                if s is self.server:
                    continue
                if readyClient.has_key(s):
                    readyClient[s] |= WRITE
                else:
                    readyClient[s] = WRITE
            
            for s in readyClient.iterkeys():
                try:
                    self.talk2client(s, bool(readyClient[s]&READ), bool(readyClient[s]&WRITE))
                except:
                    traceback.print_exc()
                    self.removeClient(s)
            
            time.sleep(0.01)
            
        self.server.close()

if __name__ == "__main__":
    try:
        #get first argument as port number
        port =  DEFAULT_PORT if len(sys.argv)==1 else int(sys.argv[1])
        FalldogServer(port=port).serve()
    except:
        traceback.print_exc()
    raw_input()
    
    